package primitive3D;

import static rayTracer.ApplicationInterface.*;

import java.util.ArrayList;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import rayTracer.Ray;
import rayTracer.Shader;
import rayTracer.Transformation;


public class Box extends Primitive3D {	
	private double width, height, depth;	
	private Point3d[] topPoints;
	private Point3d[] bottomPoints;
	private Quadrilateral intersected;
	
	/*
	 * By default, a cube is created with its base on the X-Y plane centered to
	 * the coordinates orgin, its side length is set to 1, and its position 
	 * is at (0, 0, 0.5)
	 * All this default values are defined in LibInterface
	 */
	public Box( Shader shader ){
		double sideLen = CUBE_SIDE_LEN;
		width = sideLen;
		height = sideLen;
		depth = sideLen;
		
		this.shader = shader;
		
		position = new Point3d( CUBE_POSITION );
		name = new String("");		
		components = new ArrayList<Primitive3D>();
		
		double[] possibleValues = {sideLen/2, -sideLen/2};
		int len = possibleValues.length;
		
		topPoints = new Point3d[len*len];
		bottomPoints = new Point3d[len*len];
		
		bottomPoints[0]=new Point3d(possibleValues[0],possibleValues[0], 0 );
		bottomPoints[1]=new Point3d(possibleValues[1],possibleValues[0], 0 );
		bottomPoints[2]=new Point3d(possibleValues[1],possibleValues[1], 0 );
		bottomPoints[3]=new Point3d(possibleValues[0],possibleValues[1], 0 );
		
		topPoints[0]=new Point3d(possibleValues[0],possibleValues[0], 1 );
		topPoints[1]=new Point3d(possibleValues[1],possibleValues[0], 1 );
		topPoints[2]=new Point3d(possibleValues[1],possibleValues[1], 1 );
		topPoints[3]=new Point3d(possibleValues[0],possibleValues[1], 1 );
		
		//We create the top Quadrilateral and the bottom Quadrilateral
		ArrayList<Point3d[]>genPoints = new ArrayList<Point3d[]>();
		genPoints.add(bottomPoints);
		genPoints.add(topPoints);
		for( int i=0 ; i < genPoints.size() ; i++ ){		
			Quadrilateral q = new Quadrilateral(genPoints.get(i), shader);			
			components.add(q);
		}		
		
		len = bottomPoints.length;
		
		for( int i= 0 ; i<len ; i++){
			Quadrilateral q = new Quadrilateral(bottomPoints[i],bottomPoints[(i+1)%len],topPoints[(i+1)%len],topPoints[i], shader);		
			components.add(q);
		}
		
	}
	
	public Box(double w, double h, double d, Shader shader){
		width = w;
		height = h;
		depth = d;
		position = new Point3d( CUBE_POSITION );
		name = new String("");		
		components = new ArrayList<Primitive3D>();
		this.shader = shader;
		
		int len = 4;
		topPoints = new Point3d[len];
		bottomPoints = new Point3d[len];
		
		bottomPoints[0]=new Point3d( width, height, -depth/2 );
		bottomPoints[1]=new Point3d( -width, height, -depth/2 );
		bottomPoints[2]=new Point3d( -width, -height, -depth/2 );
		bottomPoints[3]=new Point3d( width, -height, -depth/2 );
		
		topPoints[0]=new Point3d( width, height, depth/2 );
		topPoints[1]=new Point3d( -width, height, depth/2 );
		topPoints[2]=new Point3d( -width, -height, depth/2 );
		topPoints[3]=new Point3d( width, -height, depth/2 );
		
		//We create the top Quadrilateral and the bottom Quadrilateral
		ArrayList<Point3d[]>genPoints = new ArrayList<Point3d[]>();
		genPoints.add(bottomPoints);
		genPoints.add(topPoints);
		for( int i=0 ; i < genPoints.size() ; i++ ){		
			Quadrilateral q = new Quadrilateral(genPoints.get(i), shader);			
			components.add(q);
		}		
				
		for( int i= 0 ; i<len ; i++){
			Quadrilateral q = new Quadrilateral(bottomPoints[i],bottomPoints[(i+1)%len],topPoints[(i+1)%len],topPoints[i], shader);			
			components.add(q);
		}
		
	}
	
	public Box(double w, double h, double d, Point3d position, Shader shader){
		this(w, h, d, shader);
		setPosition(position); 
	}	
	public void scale(double scaleFactor){	
		scaleFromReferencePosition(this.position, scaleFactor);
	}
	
	
	@Override
	public String toString() {
		StringBuffer str = new StringBuffer();
		StringBuffer vertexStr = new StringBuffer();
		StringBuffer quadStr = new StringBuffer();
		
		for( int i=0 ; i< topPoints.length ; i++ ){
			vertexStr.append(bottomPoints[i].toString() + "\n");
			vertexStr.append(topPoints[i].toString() + "\n");			
		}	
		for( int i=0 ; i< components.size() ; i++ ){
			quadStr.append(components.get(i).toString());
		}
		str.append("Box \"" + getName() + "\" w="+width+" h="+height+" d="+depth+"\"\nVertices = \n" + vertexStr + "\nQuad = \n" + quadStr + "\nPosition = " + position );
		return str.toString();
	}	
	
	@Override
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor) {
		for( int i=0 ; i < components.size() ; i++ ){
			components.get(i).scaleFromReferencePosition(referencePosition, scaleFactor);
		}
		for( int i=0 ; i < topPoints.length ; i++ ){
			scalePointfromReferencePosition(topPoints[i], referencePosition, scaleFactor);
			scalePointfromReferencePosition(bottomPoints[i], referencePosition, scaleFactor);
		}			
		width = width*scaleFactor;
		height = height*scaleFactor;
		depth = depth*scaleFactor;
	}
	
	@Override
	public void setPosition( Point3d otherPosition ){		
		Point3d componentPosition;
		Point3d delta = new Point3d();		
		
		for(int i=0 ; i < components.size() ; i++ ){
			componentPosition = components.get(i).getPosition();			
			delta.sub(otherPosition, this.position);			
			componentPosition.add(delta);			
			components.get(i).setPosition(componentPosition);
		}
		for( int i=0 ; i < topPoints.length ; i++ ){
			topPoints[i].sub(this.position);
			topPoints[i].add(otherPosition);
			bottomPoints[i].sub(this.position);
			bottomPoints[i].add(otherPosition);
		}		
		position = new Point3d(otherPosition);
	}

	// Devuelve la distancia al punto intersectado en el nombre de
	// la funcion. Si no hay interseccion devuelve -1.  
	public double intersects(Ray ray) {
		double distance, minDistance = Double.POSITIVE_INFINITY;	
		for( Primitive3D aPrimitive : components ) {
			distance = aPrimitive.intersects(ray);
			if( 0 <= distance && distance < minDistance ) {				
				minDistance = distance;
				this.intersected = (Quadrilateral) aPrimitive;
			}
		}
		if( minDistance == Double.POSITIVE_INFINITY ) minDistance = -1;
		return minDistance;
	}

	// Devuelve un vector normal a la superficie del box que pasa por p
	// p debe ser un punto de la superficie del box
	public Vector3d getNormal(Point3d p) {
		// Uso el cuadrilatero que fue intersectado por el rayo anteriormente.
		// Trato al cuadrilatero como un triangulo. Ambos triangulos tienen la
		// misma normal que el cuadrilatero.
		return this.intersected.getNormal(p);
	}
	
	@Override
	public int transform(Transformation trans) {
		trans.translateRotate(position);
		for( int i=0 ; i < topPoints.length ; i++ ){
			trans.transformPoint(topPoints[i]);
			trans.transformPoint(bottomPoints[i]);			
		}	
		for( int i=0 ; i < components.size() ; i++ ){
			components.get(i).transform(trans);
		}
		width = width * trans.transformMatrix.getScale();
		height = height * trans.transformMatrix.getScale();
		depth = depth * trans.transformMatrix.getScale();
		return 0;
	}

}
